package com.cmict.data.bpm.service.definition;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.cmict.data.bpm.enums.definition.BpmAutoAgreeEnum;
import com.cmict.data.form.api.task.dto.FormBaseDTO;
import com.cmict.data.framework.common.pojo.PageResult;
import com.cmict.data.framework.common.util.object.PageUtils;
import com.cmict.data.framework.flowable.core.util.FlowableUtils;
import com.cmict.data.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionListReqVO;
import com.cmict.data.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
import com.cmict.data.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
import com.cmict.data.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import com.cmict.data.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import com.cmict.data.bpm.convert.definition.BpmProcessDefinitionConvert;
import com.cmict.data.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
import com.cmict.data.bpm.dal.mysql.definition.BpmProcessDefinitionExtMapper;
import com.cmict.data.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
import com.cmict.data.bpm.service.task.BpmTaskService;
import com.cmict.data.form.api.task.FormDataApi;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.common.engine.impl.util.io.BytesStreamSource;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;

import static com.cmict.data.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cmict.data.framework.common.util.collection.CollectionUtils.*;
import static com.cmict.data.framework.common.util.collection.CollectionUtils.convertMap;
import static com.cmict.data.bpm.enums.ErrorCodeConstants.*;
import static java.util.Collections.emptyList;

/**
 * 流程定义实现
 * 主要进行 Flowable {@link ProcessDefinition} 和 {@link Deployment} 的维护
 *
 * @author yunlongn
 * @author ZJQ
 * @author awesome
 */
@Service
@Validated
@Slf4j
public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionService {

    private static final String BPMN_FILE_SUFFIX = ".bpmn";

    @Resource
    private RepositoryService repositoryService;

    @Resource
    private BpmProcessDefinitionExtMapper processDefinitionMapper;
    @Resource
    @Lazy // 解决循环依赖
    private BpmTaskService taskService;
    @Resource
    private FormDataApi formDataApi;
    @Resource
    private BpmProcessDefinitionExtMapper processDefinitionExtMapper;

    @Override
    public ProcessDefinition getProcessDefinition(String id) {
        return repositoryService.getProcessDefinition(id);
    }

    @Override
    public ProcessDefinition getProcessDefinition2(String id) {
        return repositoryService.createProcessDefinitionQuery().processDefinitionId(id).singleResult();
    }

    @Override
    public ProcessDefinition getProcessDefinitionByDeploymentId(String deploymentId) {
        if (StrUtil.isEmpty(deploymentId)) {
            return null;
        }
        return repositoryService.createProcessDefinitionQuery().deploymentId(deploymentId).singleResult();
    }

    @Override
    public List<ProcessDefinition> getProcessDefinitionListByDeploymentIds(Set<String> deploymentIds) {
        if (CollUtil.isEmpty(deploymentIds)) {
            return emptyList();
        }
        return repositoryService.createProcessDefinitionQuery().deploymentIds(deploymentIds).list();
    }

    @Override
    public ProcessDefinition getActiveProcessDefinition(String key) {
        return repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).active().singleResult();
    }

    @Override
    public List<Deployment> getDeployments(Set<String> ids) {
        if (CollUtil.isEmpty(ids)) {
            return emptyList();
        }
        List<Deployment> list = new ArrayList<>(ids.size());
        for (String id : ids) {
            addIfNotNull(list, getDeployment(id));
        }
        return list;
    }

    @Override
    public Deployment getDeployment(String id) {
        if (StrUtil.isEmpty(id)) {
            return null;
        }
        return repositoryService.createDeploymentQuery().deploymentId(id).singleResult();
    }

    @Override
    public BpmnModel getBpmnModel(String processDefinitionId) {
        return repositoryService.getBpmnModel(processDefinitionId);
    }

    @Override
    public String createProcessDefinition(@Valid BpmProcessDefinitionCreateReqDTO createReqDTO) {
        // 创建 Deployment 部署
        Deployment deploy = repositoryService.createDeployment()
                .key(createReqDTO.getKey()).name(createReqDTO.getName()).category(createReqDTO.getCategory())
                .addBytes(createReqDTO.getKey() + BPMN_FILE_SUFFIX, createReqDTO.getBpmnBytes())
                .deploy();

        // 设置 ProcessDefinition 的 category 分类
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()).singleResult();
        repositoryService.setProcessDefinitionCategory(definition.getId(), createReqDTO.getCategory());
        // 注意 1，ProcessDefinition 的 key 和 name 是通过 BPMN 中的 <bpmn2:process /> 的 id 和 name 决定
        // 注意 2，目前该项目的设计上，需要保证 Model、Deployment、ProcessDefinition 使用相同的 key，保证关联性。
        //          否则，会导致 ProcessDefinition 的分页无法查询到。
        if (!Objects.equals(definition.getKey(), createReqDTO.getKey())) {
            throw exception(PROCESS_DEFINITION_KEY_NOT_MATCH, createReqDTO.getKey(), definition.getKey());
        }
        if (!Objects.equals(definition.getName(), createReqDTO.getName())) {
            throw exception(PROCESS_DEFINITION_NAME_NOT_MATCH, createReqDTO.getName(), definition.getName());
        }

        // 插入拓展表
        BpmProcessDefinitionExtDO definitionDO = BpmProcessDefinitionConvert.INSTANCE.convert2(createReqDTO)
                .setProcessDefinitionId(definition.getId());
        processDefinitionMapper.insert(definitionDO);
        return definition.getId();
    }

    @Override
    public void updateProcessDefinitionState(String id, Integer state) {
        // 激活
        if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), state)) {
            repositoryService.activateProcessDefinitionById(id, false, null);
            return;
        }
        // 挂起
        if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), state)) {
            // suspendProcessInstances = false，进行中的任务，不进行挂起。
            // 原因：只要新的流程不允许发起即可，老流程继续可以执行。
            repositoryService.suspendProcessDefinitionById(id, false, null);
            return;
        }
        log.error("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", id, state);
    }

    @Override
    public String getProcessDefinitionBpmnXML(String id) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(id);
        if (bpmnModel == null) {
            return null;
        }
        BpmnXMLConverter converter = new BpmnXMLConverter();
        return StrUtil.utf8Str(converter.convertToXML(bpmnModel));
    }
    private static class IgnoreDTDEntityResolver implements EntityResolver {
        @Override
        public InputSource resolveEntity(String publicId, String systemId)
                throws SAXException, IOException {
            return new InputSource(new ByteArrayInputStream("<?xml version='1.0' encoding='UTF-8'?>".getBytes()));
        }

    }

    @Override
    public Map<String, Object> readxmlByDom(String str, String processInstanceId) {
        List<BpmTaskRespVO> taskListByProcessInstance = taskService.getTaskListByProcessInstanceId(processInstanceId);
        String definitionKey =null;
        if(taskListByProcessInstance != null  && taskListByProcessInstance.size()>0){
            definitionKey = taskListByProcessInstance.get(0).getDefinitionKey();
        }
        SAXReader reader = new SAXReader();
        reader.setEntityResolver(new IgnoreDTDEntityResolver()); // ignore dtd
        Document doc = null; // load
        try {
            doc = reader.read(new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8)));
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        // 创建XML读取工具对象，并获取文档对象，文档内容在文章开头
//        Document doc = new SAXReader().read(new File("drinks.xml"));/buttonTy
        // 获取根元素
        Map<String, Object> isCheckeMmap = new TreeMap<>();
        isCheckeMmap.put("isChecked",false);
        isCheckeMmap.put("isShow",false);

        Element root = doc.getRootElement();
        List<Map<String,Object>> list = new ArrayList<>();
        // 打印所有元素信息
        List<Element> elements = root.elements();
        String approveAutoAgree = BpmAutoAgreeEnum.NOT_AGREE.getType();
        for (Element drink : elements) {
            if(drink.attribute("agreeId")!=null && StringUtils.isNoneBlank(drink.attribute("agreeId").getName())) {
                 approveAutoAgree = drink.attribute("agreeId").getValue();
            }
            List<Element> drink1 = drink.elements();
            for (Element drink2 : drink1) {
                if(StringUtils.isNoneBlank(definitionKey) && StringUtils.isNoneBlank(drink2.attributeValue("id")) && drink2.attributeValue("id").equals(definitionKey)){
                    List<Element> drink3 = drink2.elements();
                    for (Element drink4 : drink3) {
                        List<Element> drink5 = drink4.elements();
                        for (Element drink6 : drink5) {
                            Attribute attribute = drink6.attribute("isChecked");
                            String name = null;
                            String value = null;
                            if (attribute != null) {
                                name = attribute.getName();
                                value = attribute.getValue();
                            }
                            if(StringUtils.isNoneBlank(name) && name.equals("isChecked")){

                                if(StringUtils.isNoneBlank(value) && value.equals("true")){
                                    isCheckeMmap.put("isChecked",true);
                                }
                            }
                            Attribute showAttribute = drink6.attribute("isShow");
                            String showName = null;
                            String showValue = null;
                            if (showAttribute != null) {
                                showName = showAttribute.getName();
                                showValue = showAttribute.getValue();
                            }
                            if(StringUtils.isNoneBlank(showName) && showName.equals("isShow")){

                                if(StringUtils.isNoneBlank(showValue) && showValue.equals("true")){
                                    isCheckeMmap.put("isShow",true);
                                }
                            }
                            List<Element> drink7 = drink6.elements();
                            for (Element drink8 : drink7) {
                                if(StringUtils.isNoneBlank(drink8.attributeValue("sort"))){
                                    Map<String, Object> map = new TreeMap<>();
                                    map.put("label",drink8.attributeValue("label"));
                                    map.put("value",drink8.attributeValue("value"));
                                    map.put("sort",drink8.attributeValue("sort"));
                                    list.add(map);
                                }
                            }
                        }
                    }

                }

            }

        }
        Map<String, Object> map = new HashMap<>();
        map.put("button",list);
        map.put("carbon",isCheckeMmap);
        map.put("approveAutoAgree",approveAutoAgree);

        return map;
    }


    @Override
    public boolean isProcessDefinitionEquals(@Valid BpmProcessDefinitionCreateReqDTO createReqDTO) {
        // 校验 name、description 是否更新
        ProcessDefinition oldProcessDefinition = getActiveProcessDefinition(createReqDTO.getKey());
        if (oldProcessDefinition == null) {
            return false;
        }
        BpmProcessDefinitionExtDO oldProcessDefinitionExt = getProcessDefinitionExt(oldProcessDefinition.getId());
        if (!StrUtil.equals(createReqDTO.getName(), oldProcessDefinition.getName())
                || !StrUtil.equals(createReqDTO.getDescription(), oldProcessDefinitionExt.getDescription())
                || !StrUtil.equals(createReqDTO.getCategory(), oldProcessDefinition.getCategory())) {
            return false;
        }
        // 校验 form 信息是否更新
        if (!ObjectUtil.equal(createReqDTO.getFormType(), oldProcessDefinitionExt.getFormType())
                || !ObjectUtil.equal(createReqDTO.getFormId(), oldProcessDefinitionExt.getFormId())
                || !ObjectUtil.equal(createReqDTO.getFormConf(), oldProcessDefinitionExt.getFormConf())
                || !ObjectUtil.equal(createReqDTO.getFormFields(), oldProcessDefinitionExt.getFormFields())
                || !ObjectUtil.equal(createReqDTO.getFormCustomCreatePath(), oldProcessDefinitionExt.getFormCustomCreatePath())
                || !ObjectUtil.equal(createReqDTO.getFormCustomViewPath(), oldProcessDefinitionExt.getFormCustomViewPath())) {
            return false;
        }
        // 校验 BPMN XML 信息
        BpmnModel newModel = buildBpmnModel(createReqDTO.getBpmnBytes());
        BpmnModel oldModel = getBpmnModel(oldProcessDefinition.getId());
        //TODO  貌似 flowable 不修改这个也不同。需要看看。 sourceSystemId 不同
        if (!FlowableUtils.equals(oldModel, newModel)) {
            return false;
        }
        // 最终发现都一致，则返回 true
        return true;
    }

    /**
     * 构建对应的 BPMN Model
     *
     * @param bpmnBytes 原始的 BPMN XML 字节数组
     * @return BPMN Model
     */
    private  BpmnModel buildBpmnModel(byte[] bpmnBytes) {
        // 转换成 BpmnModel 对象
        BpmnXMLConverter converter = new BpmnXMLConverter();
        return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), true, true);
    }



    @Override
    public BpmProcessDefinitionExtDO getProcessDefinitionExt(String id) {
        return processDefinitionMapper.selectByProcessDefinitionId(id);
    }

    @Override
    public List<BpmProcessDefinitionRespVO> getProcessDefinitionList(BpmProcessDefinitionListReqVO listReqVO) {
        // 拼接查询条件
        ProcessDefinitionQuery definitionQuery = repositoryService.createProcessDefinitionQuery();
        if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), listReqVO.getSuspensionState())) {
            definitionQuery.suspended();
        } else if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), listReqVO.getSuspensionState())) {
            definitionQuery.active();
        }
        // 执行查询
        List<ProcessDefinition> processDefinitions = definitionQuery.list();
        if (CollUtil.isEmpty(processDefinitions)) {
            return Collections.emptyList();
        }

        // 获得 BpmProcessDefinitionDO Map
        List<BpmProcessDefinitionExtDO> processDefinitionDOs = processDefinitionMapper.selectListByProcessDefinitionIds(
                convertList(processDefinitions, ProcessDefinition::getId));
        Map<String, BpmProcessDefinitionExtDO> processDefinitionDOMap = convertMap(processDefinitionDOs,
                BpmProcessDefinitionExtDO::getProcessDefinitionId);
        // 执行查询，并返回
        return BpmProcessDefinitionConvert.INSTANCE.convertList3(processDefinitions, processDefinitionDOMap);
    }

    @Override
    public PageResult<BpmProcessDefinitionPageItemRespVO> getProcessDefinitionPage(BpmProcessDefinitionPageReqVO pageVO) {
        ProcessDefinitionQuery definitionQuery = repositoryService.createProcessDefinitionQuery();
        if (StrUtil.isNotBlank(pageVO.getKey())) {
            definitionQuery.processDefinitionKey(pageVO.getKey());
        }

        // 执行查询
        List<ProcessDefinition> processDefinitions = definitionQuery.orderByProcessDefinitionVersion().desc()
                .listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());

        if (CollUtil.isEmpty(processDefinitions)) {
            return new PageResult<>(emptyList(), definitionQuery.count());
        }
        // 获得 Deployment Map
        Set<String> deploymentIds = new HashSet<>();
        processDefinitions.forEach(definition -> addIfNotNull(deploymentIds, definition.getDeploymentId()));
        Map<String, Deployment> deploymentMap = getDeploymentMap(deploymentIds);

        // 获得 BpmProcessDefinitionDO Map
        List<BpmProcessDefinitionExtDO> processDefinitionDOs = processDefinitionMapper.selectListByProcessDefinitionIds(
                convertList(processDefinitions, ProcessDefinition::getId));
        Map<String, BpmProcessDefinitionExtDO> processDefinitionDOMap = convertMap(processDefinitionDOs,
                BpmProcessDefinitionExtDO::getProcessDefinitionId);

        // 获得 Form Map
        Set<Long> formIds = convertSet(processDefinitionDOs, BpmProcessDefinitionExtDO::getFormId);
        //Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds);
        Map<Long, FormBaseDTO> formMap = formDataApi.getFormMap(formIds);

        // 拼接结果
        long definitionCount = definitionQuery.count();
        return new PageResult<>(BpmProcessDefinitionConvert.INSTANCE.convertList(processDefinitions, deploymentMap,
                processDefinitionDOMap, formMap), definitionCount);
    }

    @Override
    public List<BpmProcessDefinitionExtDO> getProcessDefinitionExt(Long formId) {
        List<BpmProcessDefinitionExtDO> bpmProcessDefinitionExtDOS = processDefinitionExtMapper.selectListByFormId(formId);
        if (CollectionUtils.isEmpty(bpmProcessDefinitionExtDOS)) {
            throw exception(PROCESS_DEFINITION_NOT_EXISTS, formId);
        }
        return bpmProcessDefinitionExtDOS;
    }

}
